#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <memory.h>
#include <math.h>

#include <errno.h>
#include <include/pf_errno.h>
#include <include/pf_base64.h>
#include <include/pf_internal.h>


/* base64 encode table the Stationary 62 chars */
static const  char basic_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

/* base64 decode table */
static const unsigned char index_64[128] = {
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
      52,   53,   54,   55,   56,   57,   58,   59,   60,   61, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff,    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,
      15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,
      41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51, 0xff, 0xff, 0xff, 0xff, 0xff
};

/*********************************************************************************************
Function Name:  pf_check_last_2
Description  :  check last_2 world is valid,the word should be different two word.
                each between '! ' and '~'
                except a~z A~Z 0~9 and '='
Inputs       :  const char* last_2: the last2 string
Outputs      :             int32_t: 0 for ok
                                    1 for error
ErrorCodes   :
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
huyq                   2011-06-21                    create
**********************************************************************************************/
static int32_t pf_check_last_2(const char* last_2);

/*********************************************************************************************/

int32_t pf_base64_encode(const void* input,
                         int32_t input_size,
                         char* output,
                         int32_t output_size,
                         const char* last_2)
{
    int   i       = 0;
    int   j       = 0;
    char* input_c = NULL;
    char  flag_64[64];

    assert(input);
    assert(output);
    assert(last_2);
    assert(input_size >= 0);
    assert(output_size >= 0);

    if(output_size < (input_size * 4 / 3))
    {
        return PF_EINVAL;
    }

    if(pf_check_last_2(last_2))
    {
         return PF_EINVAL;
    }

    memcpy (flag_64,basic_64,62);
    flag_64[62] = last_2[0];
    flag_64[63] = last_2[1];
    input_c = (char*) input;

    for(i = 0; i < input_size - input_size % 3; i += 3)
    {
        output[j++] = flag_64[(input_c[i] >> 2) & 0x3F];
        output[j++] = flag_64[((input_c[i] << 4) & 0x30) + ((input_c[i + 1] >> 4) & 0xF)];
        output[j++] = flag_64[((input_c[i + 1] << 2) & 0x3C) + ((input_c[i + 2] >> 6) & 0x3)];
        output[j++] = flag_64[input_c[i + 2] & 0x3F];
    }

    if(input_size % 3 == 1)
    {
        output[j++] = flag_64[(input_c[i] >> 2) & 0x3F];
        output[j++] = flag_64[(input_c[i] << 4) & 0x30];
        output[j++] = '=';
        output[j++] = '=';
    }
    else if(input_size % 3 == 2)
    {
        output[j++] = flag_64[(input_c[i] >> 2) & 0x3F];
        output[j++] = flag_64[((input_c[i] << 4) & 0x30) + ((input_c[i + 1] >> 4) & 0xF)];
        output[j++] = flag_64[(input_c[i + 1] << 2) & 0x3C];
        output[j++] = '=';
    }

    if(j < output_size)
    {
          output[j] = '\0';
    }

    return 0;
}

int32_t pf_base64_decode(const  char* input,
                         int32_t input_size,
                         void* output,
                         int32_t output_size,
                         const char* last_2)
{
    int   i        = 0;
    int   j        = 0;
    char* output_c = NULL;
    unsigned char decode_64[256];

    assert(input);
    assert(output);
    assert(last_2);
    assert(input_size % 4 == 0);
    assert(input_size >= 0);
    assert(output_size >= 0);


    if(output_size < (input_size * 3 / 4))
    {
        return PF_EINVAL;
    }

    if(pf_check_last_2(last_2))
    {
        return PF_EINVAL;
    }

    memcpy(decode_64,index_64,128);
    decode_64[(uint16_t)last_2[0]] = 62;
    decode_64[(uint16_t)last_2[1]] = 63;
    output_c = (char*) output;

    for(i=0 ; i < input_size - 4; i += 4)
    {
        output_c[j++] = decode_64[input[i] & 0x7F] << 2 | decode_64[input[i + 1] & 0x7F] >> 4;
        output_c[j++] = decode_64[input[i + 1] & 0x7F] << 4 | decode_64[input[i + 2] & 0x7F] >> 2;
        output_c[j++] = decode_64[input[i + 2] & 0x7F] << 6 | decode_64[input[i + 3] & 0x7F];
    }
    if(input_size - 4 >= 0)
    {
        if(input[i+3] == '=' && input[i + 2] != '=')
        {
            output_c[j++] = decode_64[input[i] & 0x7F] << 2 
                            | decode_64[input[i + 1] & 0x7F] >> 4;
            output_c[j++] = decode_64[input[i + 1] & 0x7F] << 4 
                            | decode_64[input[i + 2] & 0x7F] >> 2;
        }
        else if(input[i+2] == '=' && input[i+3] == '=')
        {
            output_c[j++] = decode_64[input[i]&0x7F] << 2 
                            | decode_64[input[i + 1]&0x7F] >> 4;
        }
        else
        {
            output_c[j++] = decode_64[input[i] & 0x7F] << 2 
                            | decode_64[input[i + 1] & 0x7F] >> 4;
            output_c[j++] = decode_64[input[i + 1] & 0x7F] << 4 
                            | decode_64[input[i + 2] & 0x7F] >> 2;
            output_c[j++] = decode_64[input[i + 2] & 0x7F] << 6 
                            | decode_64[input[i + 3] & 0x7F];
        }
    }
    if(j < output_size)
    {
        output_c[j] = '\0';
    }

    return 0;
}

int32_t pf_base64_get_len(int32_t input_len, enum PF_BASE64_CALC_TYPE type)
{
    assert(input_len >= 0);

    if(type == 1)
    {
         return input_len % 3 == 0 ? input_len * 4 / 3 
                                 : (int32_t)(floor((input_len - input_len % 3) * 4.0 / 3)) + 4;
    }
    else if(type==2)
    {
         return input_len % 4 == 0 ? input_len * 3 / 4 : PF_EINVAL;
    }
    else
    {
        return PF_EINVAL;
    }
}

/*********************************************************************************************/

static int32_t pf_check_last_2(const char* last_2)
{
    assert(last_2);

    if(strlen(last_2) != 2)
    {
        return 1;
    }

    if(last_2[0] == last_2[1])
    {
        return 1;
    }

    if(last_2[0] < '!' || last_2[0] > '~')
    {
        return 1;
    }

    if(last_2[1] < '!' || last_2[1] > '~')
    {
        return 1;
    }

    if((last_2[0] >= '0' && last_2[0] <= '9') 
       || (last_2[0] >= 'A' && last_2[0] <= 'Z') 
       || (last_2[0] >= 'a' && last_2[0] <= 'z') || (last_2[0] == '='))
    {
        return 1;
    }

    if((last_2[1] >= '0' && last_2[1] <= '9') 
       || (last_2[1] >= 'A' && last_2[1] <= 'Z') 
       || (last_2[1] >= 'a' && last_2[1] <= 'z') || (last_2[1] == '='))
    {
        return 1;
    }

    return 0;
}
